﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.IO.Ports;
using System.Threading;
using System.Text.RegularExpressions;

namespace lt_pmbc_001.Helpers
{
    #region MANAGER ENUMS

    /// <summary>
    /// Enumeration to hold our transmission types
    /// </summary>
    public enum TransmissionType
    {
        Text,
        Hex
    }

    /// <summary>
    /// Enumeration to hold our message types
    /// </summary>
    public enum MessageType
    {
        Incoming,
        Outgoing,
        Normal,
        Warning,
        Error
    };

    #endregion

    public class CommunicationManager
    {
        #region MANAGER VARIABLES

        private string _baudRate = string.Empty;
        private string _parity = string.Empty;
        private string _stopBits = string.Empty;
        private string _dataBits = string.Empty;
        private string _portName = string.Empty;
        private TransmissionType _transType;

        //global manager variables
        private Color[] MessageColor = { Color.Blue, Color.Green, Color.Black, Color.Orange, Color.Red };
        private SerialPort comPort = new SerialPort();
        public EventHandler Handler { get; set; }

        #endregion

        #region MANAGER PROPERTIES

        /// <summary>
        /// A measure of the speed of serial communication, roughly equivalent to bits per second.
        /// </summary>
        public string BaudRate
        {
            get { return _baudRate; }
            set { _baudRate = value; }
        }

        /// <summary>
        /// The even or odd quality of the number of 1's or 0's in a binary code, 
        /// often used to determine the integrity of data especially after transmission.
        /// </summary>
        public string Parity
        {
            get { return _parity; }
            set { _parity = value; }
        }

        /// <summary>
        /// A bit that signals the end of a transmission unit
        /// </summary>
        public string StopBits
        {
            get { return _stopBits; }
            set { _stopBits = value; }
        }

        /// <summary>
        /// The number of bits used to represent one character of data
        /// </summary>
        public string DataBits
        {
            get { return _dataBits; }
            set { _dataBits = value; }
        }

        /// <summary>
        /// The port with which we're communicating through, i.e; COM1, COM2, etc.
        /// </summary>
        public string PortName
        {
            get { return _portName; }
            set { _portName = value; }
        }

        /// <summary>
        /// Property to hold our TransmissionType of our manager class
        /// </summary>
        public TransmissionType CurrentTransmissionType
        {
            get { return _transType; }
            set { _transType = value; }
        }

        public List<string> GetParityValues()
        {
            var lst = new List<string>();
            foreach (string str in Enum.GetNames(typeof(Parity)))
            {
                lst.Add(str);
            }
            return lst;
        }

        public List<string> GetStopBitValues()
        {
            var lst = new List<string>();
            foreach (string str in Enum.GetNames(typeof(StopBits)))
            {
                lst.Add(str);
            }
            return lst;
        }

        public List<string> GetPortNameValues()
        {
            var lst = new List<string>();
            foreach (string str in SerialPort.GetPortNames())
            {
                lst.Add(str);
            }
            return lst;
        }

        #endregion

        #region MANAGER CONSTRUCTORS

        /// <summary>
        /// Constructor to set the properties of our Manager Class
        /// </summary>
        /// <param name="baud">Desired BaudRate</param>
        /// <param name="par">Desired Parity</param>
        /// <param name="sBits">Desired StopBits</param>
        /// <param name="dBits">Desired DataBits</param>
        /// <param name="name">Desired PortName</param>
        public CommunicationManager(string baud, string par, string sBits, string dBits, string name, RichTextBox rtb)
        {
            _baudRate = baud;
            _parity = par;
            _stopBits = sBits;
            _dataBits = dBits;
            _portName = name;
            comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);
        }

        /// <summary>
        /// Comstructor to set the properties of our serial port communicator to nothing
        /// </summary>
        public CommunicationManager()
        {
            _baudRate = string.Empty;
            _parity = string.Empty;
            _stopBits = string.Empty;
            _dataBits = string.Empty;
            _portName = "COM1";
            comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);
        }

        #endregion

        #region DATA RECEIVED

        /// <summary>
        /// Method that will be called when theres data waiting in the buffer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // Determine the mode the user selected (binary/string)
            switch (CurrentTransmissionType)
            {
                // User chose string
                case TransmissionType.Text:
                    // Read data waiting in the buffer
                    string msg = comPort.ReadLine();
                    GlobalVariables.Logger.Info("msg: " + msg);

                    // Modem notify to PC there is a SMS received
                    if (msg.StartsWith("+CMTI"))
                    {
                        var cab = msg.Split(new char[] { ',' })[1];
                        // Send command to read the content of this SMS
                        WriteData("AT+CMGR=" + cab + "\r");
                    }
                    // Modem sent content of SMS 
                    else if (msg.StartsWith("+CMGR"))
                    {
                        // Get time
                        var compHeaderSMS = Regex.Split(msg, "\"*\"");
                        var timeSMS = compHeaderSMS[compHeaderSMS.Length - 2].Trim();
                        // Read to next offset in buffer
                        msg = comPort.ReadTo("OK\r\n").Replace("\r\n", string.Empty);
                        // Dispatch the data to the user
                        DispatchData(MessageType.Incoming, timeSMS + "#" + msg);
                        // Delete SMS at position 1 so that the next SMS received at this position
                        WriteData("AT+CMGD=1\r");
                    }
                    
                    break;

                // User chose binary
                case TransmissionType.Hex:
                    // Retrieve number of bytes in the buffer
                    int bytes = comPort.BytesToRead;
                    // Create a byte array to hold the awaiting data
                    byte[] comBuffer = new byte[bytes];
                    // Read the data and store it
                    comPort.Read(comBuffer, 0, bytes);
                    // Dispatch the data to the user
                    DispatchData(MessageType.Incoming, ByteToHex(comBuffer));
                    break;

                default:
                    // Read data waiting in the buffer
                    string str = comPort.ReadLine();
                    // Dispatch the data to the user
                    DispatchData(MessageType.Incoming, str);
                    break;
            }
        }

        /// <summary>
        /// Method to Dispatch the data to & from the port on the screen
        /// </summary>
        /// <param name="type">MessageType of the message</param>
        /// <param name="msg">Message to Dispatch</param>
        [STAThread]
        private void DispatchData(MessageType type, string msg)
        {
            this.Handler(this, new TransferDataEvent(type, msg));
        }

        /// <summary>
        /// Method to convert a byte array into a hex string
        /// </summary>
        /// <param name="comByte">byte array to convert</param>
        /// <returns>a hex string</returns>
        private string ByteToHex(byte[] comByte)
        {
            // Create a new StringBuilder object
            StringBuilder builder = new StringBuilder(comByte.Length * 3);
            // Loop through each byte in the array
            foreach (byte data in comByte)
            {
                //convert the byte to a string and add to the stringbuilder
                builder.Append(Convert.ToString(data, 16).PadLeft(2, '0').PadRight(3, ' '));
            }
            return builder.ToString().ToUpper();
        }

        #endregion

        #region WRITE DATA

        public void WriteData(string msg)
        {
            switch (CurrentTransmissionType)
            {
                case TransmissionType.Text:
                    // First make sure the port is open if its not open then open it
                    if (!comPort.IsOpen)
                    {
                        comPort.Open();
                    }
                    // Send the message to the port
                    comPort.Write(msg);
                    // Dispatch the message
                    //DispatchData(MessageType.Outgoing, msg + "\n");
                    break;

                case TransmissionType.Hex:
                    try
                    {
                        // Convert the message to byte array
                        byte[] newMsg = HexToByte(msg);
                        // Send the message to the port
                        comPort.Write(newMsg, 0, newMsg.Length);
                        // Convert back to hex and Dispatch
                        //DispatchData(MessageType.Outgoing, ByteToHex(newMsg) + "\n");
                    }
                    catch (FormatException ex)
                    {
                        // Dispatch error message
                        //DispatchData(MessageType.Error, ex.Message);
                    }
                    break;

                default:
                    // First make sure the port is open if its not open then open it
                    if (!comPort.IsOpen)
                    {
                        comPort.Open();
                    }
                    // Send the message to the port
                    comPort.Write(msg);
                    // Dispatch the message
                    //DispatchData(MessageType.Outgoing, msg + "\n");
                    break;
            }
        }

        /// <summary>
        /// Method to convert hex string into a byte array
        /// </summary>
        /// <param name="msg">string to convert</param>
        /// <returns>a byte array</returns>
        private byte[] HexToByte(string msg)
        {
            // Remove any spaces from the string
            msg = msg.Replace(" ", "");
            // Create a byte array the length of the divided by 2 (Hex is 2 characters in length)
            byte[] comBuffer = new byte[msg.Length / 2];
            // Loop through the length of the provided string
            for (int i = 0; i < msg.Length; i += 2)
            {
                // Convert each set of 2 characters to a byte and add to the array
                comBuffer[i / 2] = (byte)Convert.ToByte(msg.Substring(i, 2), 16);
            }
            return comBuffer;
        }

        #endregion

        #region OPEN PORT

        public bool OpenPort()
        {
            try
            {
                // First check if the port is already open if its open then close it
                if (comPort.IsOpen)
                {
                    comPort.Close();
                }

                // Set the properties of our SerialPort Object
                comPort.BaudRate = int.Parse(_baudRate);    //BaudRate
                comPort.DataBits = int.Parse(_dataBits);    //DataBits
                comPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), _stopBits);    //StopBits
                comPort.Parity = (Parity)Enum.Parse(typeof(Parity), _parity);    //Parity
                comPort.PortName = _portName;   //PortName
                // Now open the port
                comPort.Open();
                // Dispatch message
                //DispatchData(MessageType.Normal, "Port opened at " + DateTime.Now + "\n");
                return true;
            }
            catch (Exception ex)
            {
                //DispatchData(MessageType.Error, ex.Message);
                return false;
            }
        }

        public bool ClosePort()
        {
            try
            {
                // First check if the port is already open if its open then close it
                if (comPort.IsOpen)
                {
                    comPort.Close();
                }
                // Dispatch message
                //DispatchData(MessageType.Normal, "Port closed at " + DateTime.Now + "\n");
                return true;
            }
            catch (Exception ex)
            {
                //DispatchData(MessageType.Error, ex.Message);
                return false;
            }
        }

        #endregion
    }
}
